package com.may.utils;

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import lombok.experimental.UtilityClass;
import org.apache.commons.io.Charsets;
import org.springframework.lang.Nullable;
import org.springframework.util.DigestUtils;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

/**
 * 加密相关工具类直接使用Spring util封装，减少jar依赖
 *
 * @author stick
 */
@UtilityClass
public class DigestUtil extends DigestUtils {

	private final byte[] PLUSBYTE = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
	private final String ALGORITHM = "AES/CBC/PKCS5Padding";
	private final String DEFAULTIV = "IB9N75V82Q0KJ3BK";

	private final char[] HEX_CODE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

	/**
	 * Calculates the MD5 digest and returns the value as a 32 character hex string.
	 *
	 * @param data Data to digest
	 * @return MD5 digest as a hex string
	 */
	public String md5Hex(final String data) {
		return DigestUtils.md5DigestAsHex(data.getBytes(Charsets.UTF_8));
	}

	/**
	 * Return a hexadecimal string representation of the MD5 digest of the given bytes.
	 *
	 * @param bytes the bytes to calculate the digest over
	 * @return a hexadecimal digest string
	 */
	public String md5Hex(final byte[] bytes) {
		return DigestUtils.md5DigestAsHex(bytes);
	}

	/**
	 * sha1Hex
	 *
	 * @param data Data to digest
	 * @return digest as a hex string
	 */
	public String sha1Hex(String data) {
		return sha1Hex(data.getBytes(Charsets.UTF_8));
	}

	/**
	 * sha1Hex
	 *
	 * @param bytes Data to digest
	 * @return digest as a hex string
	 */
	public String sha1Hex(final byte[] bytes) {
		return digestHex("SHA-1", bytes);
	}

	/**
	 * SHA224Hex
	 *
	 * @param data Data to digest
	 * @return digest as a hex string
	 */
	public String sha224Hex(String data) {
		return sha224Hex(data.getBytes(Charsets.UTF_8));
	}

	/**
	 * SHA224Hex
	 *
	 * @param bytes Data to digest
	 * @return digest as a hex string
	 */
	public String sha224Hex(final byte[] bytes) {
		return digestHex("SHA-224", bytes);
	}

	/**
	 * sha256Hex
	 *
	 * @param data Data to digest
	 * @return digest as a hex string
	 */
	public String sha256Hex(String data) {
		return sha256Hex(data.getBytes(Charsets.UTF_8));
	}

	/**
	 * sha256Hex
	 *
	 * @param bytes Data to digest
	 * @return digest as a hex string
	 */
	public String sha256Hex(final byte[] bytes) {
		return digestHex("SHA-256", bytes);
	}

	/**
	 * sha384Hex
	 *
	 * @param data Data to digest
	 * @return digest as a hex string
	 */
	public String sha384Hex(String data) {
		return sha384Hex(data.getBytes(Charsets.UTF_8));
	}

	/**
	 * sha384Hex
	 *
	 * @param bytes Data to digest
	 * @return digest as a hex string
	 */
	public String sha384Hex(final byte[] bytes) {
		return digestHex("SHA-384", bytes);
	}

	/**
	 * sha512Hex
	 *
	 * @param data Data to digest
	 * @return digest as a hex string
	 */
	public String sha512Hex(String data) {
		return sha512Hex(data.getBytes(Charsets.UTF_8));
	}

	/**
	 * sha512Hex
	 *
	 * @param bytes Data to digest
	 * @return digest as a hex string
	 */
	public String sha512Hex(final byte[] bytes) {
		return digestHex("SHA-512", bytes);
	}

	/**
	 * digest Hex
	 *
	 * @param algorithm 算法
	 * @param bytes     Data to digest
	 * @return digest as a hex string
	 */
	public String digestHex(String algorithm, byte[] bytes) {
		try {
			MessageDigest md = MessageDigest.getInstance(algorithm);
			return encodeHex(md.digest(bytes));
		} catch (NoSuchAlgorithmException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * hmacMd5 Hex
	 *
	 * @param data Data to digest
	 * @param key  key
	 * @return digest as a hex string
	 */
	public String hmacMd5Hex(String data, String key) {
		return hmacMd5Hex(data.getBytes(Charsets.UTF_8), key);
	}

	/**
	 * hmacMd5 Hex
	 *
	 * @param bytes Data to digest
	 * @param key   key
	 * @return digest as a hex string
	 */
	public String hmacMd5Hex(final byte[] bytes, String key) {
		return digestHMacHex("HmacMD5", bytes, key);
	}

	/**
	 * hmacSha1 Hex
	 *
	 * @param data Data to digest
	 * @param key  key
	 * @return digest as a hex string
	 */
	public String hmacSha1Hex(String data, String key) {
		return hmacSha1Hex(data.getBytes(Charsets.UTF_8), key);
	}

	/**
	 * hmacSha1 Hex
	 *
	 * @param bytes Data to digest
	 * @param key   key
	 * @return digest as a hex string
	 */
	public String hmacSha1Hex(final byte[] bytes, String key) {
		return digestHMacHex("HmacSHA1", bytes, key);
	}

	/**
	 * hmacSha224 Hex
	 *
	 * @param data Data to digest
	 * @param key  key
	 * @return digest as a hex string
	 */
	public String hmacSha224Hex(String data, String key) {
		return hmacSha224Hex(data.getBytes(Charsets.UTF_8), key);
	}

	/**
	 * hmacSha224 Hex
	 *
	 * @param bytes Data to digest
	 * @param key   key
	 * @return digest as a hex string
	 */
	public String hmacSha224Hex(final byte[] bytes, String key) {
		return digestHMacHex("HmacSHA224", bytes, key);
	}

	/**
	 * hmacSha256 Hex
	 *
	 * @param data Data to digest
	 * @param key  key
	 * @return digest as a hex string
	 */
	public String hmacSha256Hex(String data, String key) {
		return hmacSha256Hex(data.getBytes(Charsets.UTF_8), key);
	}

	/**
	 * hmacSha256 Hex
	 *
	 * @param bytes Data to digest
	 * @param key   key
	 * @return digest as a hex string
	 */
	public String hmacSha256Hex(final byte[] bytes, String key) {
		return digestHMacHex("HmacSHA256", bytes, key);
	}

	/**
	 * hmacSha384 Hex
	 *
	 * @param data Data to digest
	 * @param key  key
	 * @return digest as a hex string
	 */
	public String hmacSha384Hex(String data, String key) {
		return hmacSha384Hex(data.getBytes(Charsets.UTF_8), key);
	}

	/**
	 * hmacSha384 Hex
	 *
	 * @param bytes Data to digest
	 * @param key   key
	 * @return digest as a hex string
	 */
	public String hmacSha384Hex(final byte[] bytes, String key) {
		return digestHMacHex("HmacSHA384", bytes, key);
	}

	/**
	 * hmacSha512 Hex
	 *
	 * @param data Data to digest
	 * @param key  key
	 * @return digest as a hex string
	 */
	public String hmacSha512Hex(String data, String key) {
		return hmacSha512Hex(data.getBytes(Charsets.UTF_8), key);
	}

	/**
	 * hmacSha512 Hex
	 *
	 * @param bytes Data to digest
	 * @param key   key
	 * @return digest as a hex string
	 */
	public String hmacSha512Hex(final byte[] bytes, String key) {
		return digestHMacHex("HmacSHA512", bytes, key);
	}

	/**
	 * digest HMac Hex
	 *
	 * @param algorithm 算法
	 * @param bytes     Data to digest
	 * @return digest as a hex string
	 */
	public String digestHMacHex(String algorithm, final byte[] bytes, String key) {
		SecretKey secretKey = new SecretKeySpec(key.getBytes(Charsets.UTF_8), algorithm);
		try {
			Mac mac = Mac.getInstance(secretKey.getAlgorithm());
			mac.init(secretKey);
			return encodeHex(mac.doFinal(bytes));
		} catch (NoSuchAlgorithmException | InvalidKeyException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * encode Hex
	 *
	 * @param bytes Data to Hex
	 * @return bytes as a hex string
	 */
	public String encodeHex(byte[] bytes) {
		StringBuilder r = new StringBuilder(bytes.length * 2);
		for (byte b : bytes) {
			r.append(HEX_CODE[(b >> 4) & 0xF]);
			r.append(HEX_CODE[(b & 0xF)]);
		}
		return r.toString();
	}


	/**
	 * 比较字符串，避免字符串因为过长，产生耗时
	 *
	 * @param a String
	 * @param b String
	 * @return 是否相同
	 */
	public boolean slowEquals(@Nullable String a, @Nullable String b) {
		if (a == null || b == null) {
			return false;
		}
		return slowEquals(a.getBytes(Charsets.UTF_8), b.getBytes(Charsets.UTF_8));
	}

	/**
	 * 比较 byte 数组，避免字符串因为过长，产生耗时
	 *
	 * @param a byte array
	 * @param b byte array
	 * @return 是否相同
	 */
	public boolean slowEquals(@Nullable byte[] a, @Nullable byte[] b) {
		if (a == null || b == null) {
			return false;
		}
		if (a.length != b.length) {
			return false;
		}
		int diff = a.length ^ b.length;
		for (int i = 0; i < a.length && i < b.length; i++) {
			diff |= a[i] ^ b[i];
		}
		return diff == 0;
	}

	/**
	 * 自定义加密 先MD5再SHA1
	 *
	 * @param data 数据
	 * @return String
	 */
	public String encrypt(String data) {
		return sha1Hex(md5Hex(data));
	}

	/**
	 * sha1加密操作 兼容php的sha1函数
	 *
	 * @param content 待加密内容
	 * @return 返回String
	 */
	public String encryptForPhp(String content) {
		try {
			// Create MD5 Hash
			MessageDigest digest = MessageDigest.getInstance("SHA-1");
			digest.update(content.getBytes());
			byte messageDigest[] = digest.digest();

			// Create Hex String
			StringBuffer hexString = new StringBuffer();
			for (byte b : messageDigest) hexString.append(String.format("%02X", 0xFF & b));

			return hexString.toString().toLowerCase();

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * 与php加密方式一致
	 *
	 * @param content
	 * @return
	 */
	public String phpInterPayUserForSha1(String content) {
		return encryptForPhp(encryptForPhp(content) + "asd64x6584a__asd120399dkcx/*/asd565").toUpperCase();
	}

	/**
	 * 加密邮箱,手机号
	 *
	 * @param strToEncrypt 明文
	 * @param secret       秘钥
	 * @return
	 *
	 */
	public String aesEncrypt(String strToEncrypt, String secret) {
		AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, padd(secret,32), DEFAULTIV.getBytes());
		return aes.encryptBase64(strToEncrypt);
	}

	/**
	 * 解密邮箱,手机号
	 *
	 * @param strToDecrypt 密文
	 * @param secret       秘钥
	 * @return 解密后明文
	 */
	public String aesDecrypt(String strToDecrypt, String secret) {
		AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, padd(secret,32), DEFAULTIV.getBytes());
		return aes.decryptStr(strToDecrypt);
	}

	/**
	 * 根据key转换为数组,长度不足补0
	 *
	 * @param key
	 * @param length
	 * @return byte[]
	 */
	private byte[] padd(String key, int length){
		int plus = length - key.length();
		byte[] data = key.getBytes(StandardCharsets.UTF_8);
		byte[] raw = new byte[length];
		for (int i = 0; i < length; i++) {
			if (data.length > i) {
				raw[i] = data[i];
			} else {
				raw[i] = PLUSBYTE[plus - 1];
			}
		}
		return raw;
	}

	/**
	 * 生成随机字符串
	 *
	 * @param length
	 * @return
	 */
	public String getRandomString(int length) throws NoSuchAlgorithmException {
		String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		Random random = SecureRandom.getInstanceStrong();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(62);
			sb.append(str.charAt(number));
		}
		return sb.toString();
	}
}
